package biolabs.gui;

import java.awt.Component;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import scala.collection.JavaConversions;
import biolabs.core.BioContainer;
import biolabs.core.BioObject;
import biolabs.core.Experiment;
import biolabs.core.ExperimentListener;

/**
 * GUI component that displays the tree (hierarchy) of bio-objects
 * in the currently selected experiment.
 *
 * @author Tomas Mikula
 */
class ExperimentExplorer implements UIComponent, ExperimentSelectionListener {

	private static final String UNREACHABLE_METHOD =
			"This method should never have been called";

	private static final TreeModel EMPTY_MODEL = new TreeModel() {

		@Override
		public void addTreeModelListener(TreeModelListener l) {
			// do nothing
		}

		@Override
		public void removeTreeModelListener(TreeModelListener l) {
			// do nothing
		}

		@Override
		public Object getRoot() {
			return null;
		}

		@Override
		public Object getChild(Object parent, int index) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

		@Override
		public int getChildCount(Object parent) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

		@Override
		public int getIndexOfChild(Object parent, Object child) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

		@Override
		public boolean isLeaf(Object node) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

		@Override
		public void valueForPathChanged(TreePath path, Object newValue) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

	};


	private final JTree tree = new JTree(EMPTY_MODEL);
	private final JScrollPane scrollPane = new JScrollPane(tree);
	private Experiment currentExperiment = null;
	private final ExperimentListener eListener = new ExperimentListener() {

		@Override
		public void experimentClosed(Experiment exp) {
			// the UI is cleared and the experiment listener removed
			// as a result of experiment deselection =>
			// do nothing
		}

		@Override
		public void experimentStarted(Experiment exp) {
			// do nothing
		}

		@Override
		public void experimentStopped(Experiment exp) {
			assert exp == currentExperiment : "listening to " + exp + " that is not the current one " + currentExperiment;
			((ExperimentTreeModel) tree.getModel()).refresh();
		}

	};

	@Override
	public Component getContent() {
		return scrollPane;
	}

	@Override
	public void experimentSelected(Experiment exp) {

		if(currentExperiment == exp)
			return;

		if(currentExperiment != null)
			currentExperiment.removeListener(eListener);

		currentExperiment = exp;

		if(exp == null)
			tree.setModel(EMPTY_MODEL);
		else {
			tree.setModel(new ExperimentTreeModel(exp));
			exp.addListener(eListener);
		}
		tree.validate();
	}


	private static class ExperimentTreeModel implements TreeModel {

		private final Experiment exp;
		private final List<TreeModelListener> listeners;

		public ExperimentTreeModel(Experiment exp) {
			this.exp = exp;
			listeners = new LinkedList<TreeModelListener>();
		}

		@Override
		public void addTreeModelListener(TreeModelListener l) {
			listeners.add(l);
		}

		@Override
		public void removeTreeModelListener(TreeModelListener l) {
			listeners.remove(l);
		}

		@Override
		public Object getChild(Object parent, int index) {
			return ((BioContainer<?>) parent).getChild(index);
		}

		@Override
		public int getChildCount(Object parent) {
			return ((BioContainer<?>) parent).getChildCount();
		}

		@Override
		public int getIndexOfChild(Object parent, Object child) {
			Iterable<? extends BioObject> children = JavaConversions.asJavaIterable(((BioContainer<?>) parent).getChildren());
			int i = 0;
			for(BioObject ch: children) {
				if(ch == child)
					return i;
				++i;
			}
			return -1;
		}

		@Override
		public Object getRoot() {
			return exp.getRoot();
		}

		@Override
		public boolean isLeaf(Object node) {
			return !(node instanceof BioContainer<?>);
		}

		@Override
		public void valueForPathChanged(TreePath path, Object newValue) {
			throw new AssertionError(UNREACHABLE_METHOD);
		}

		void refresh() {
			for(TreeModelListener l: listeners)
				l.treeStructureChanged(new TreeModelEvent(exp, new TreePath(exp.getRoot())));
		}
	}

}
